﻿#include "context.h"
#include "target.h"
#include "shape.h"
#include "draw.h"
#include <thread>
#include <chrono>
#include <stack>
#include <map>
#include <queue>
#include <glad/glad.h>
#include <GLFW/glfw3.h>

using namespace std;
using namespace anip;

MonitorInfo::MonitorInfo(void* hnd):
	_handle(hnd)
{}

vec2 MonitorInfo::position() const
{
	int x, y;
	glfwGetMonitorPos((GLFWmonitor*)_handle, &x, &y);
	return { (float)x, (float)y };
}

vec2 MonitorInfo::resolution() const
{
	int w, h;
	glfwGetMonitorWorkarea((GLFWmonitor*)_handle, nullptr, nullptr, &w, &h);
	return { (float)w, (float)h };
}

vec2 MonitorInfo::physicalSize() const
{
	int w, h;
	glfwGetMonitorPhysicalSize((GLFWmonitor*)_handle, &w, &h);
	return { (float)w, (float)h };
}

vec2 MonitorInfo::contentScale() const
{
	float w, h;
	glfwGetMonitorContentScale((GLFWmonitor*)_handle, &w, &h);
	return { w, h };
}

const wstring MonitorInfo::name() const
{
	return utf8s2ws(glfwGetMonitorName((GLFWmonitor*)_handle));
}

struct Context::Impl
{
	static GLFWwindow* _window;
	static vec2 _size, _contentScale;
	static wstring _title;
	static bool _inited;
	static double _dt, _dampedDt, _frameStart;

	static Shape* _screen;
	static std::stack<RenderBuffer*> _targets;
	static RenderBuffer _defTarget; // dummy target! uses default ctor

	// events
	static queue<UserEvent> _events;

	// mouse events
	static vec2 _mousePos;
	static set<MouseButton> _mbuttons;

	// key
	static set<Key> _holdingKeys;

	// scissor test rectangle
	static vec2 _scissor, _scissorSize;

	static map<KnownCursor, GLFWcursor*> _knownCursors;
	static GLFWcursor* _cursor;

	// callbacks
	static void _cursorPosCallback(GLFWwindow* w, double x, double y)
	{
		_mousePos = vec2{ (float)x, (float)y } * _contentScale;

		UserEvent ev;
		ev.type = UserEvent::Type::MouseMove;
		ev.pos = _mousePos;
		_events.push(ev);
	}

	static void _scrollCallback(GLFWwindow* w, double x, double y)
	{
		UserEvent ev;
		ev.type = UserEvent::Type::Scroll;
		ev.scroll = { (float)x, (float)y };
		_events.push(ev);
	}

	static void _unicodeCallback(GLFWwindow* w, unsigned int code)
	{
		UserEvent ev;
		ev.type = UserEvent::Type::UnicodeInput;
		ev.unicode = code;
		_events.push(ev);
	}

	static void _dropCallback(GLFWwindow* w, int n, const char* paths[])
	{
		UserEvent ev;
		ev.type = UserEvent::Type::Drop;
		ev.drop.count = n;
		ev.drop.paths = paths;
		_events.push(ev);
	}

	static void _mouseActionCallback(GLFWwindow* w, int button, int action, int mods)
	{
		if ((ButtonAction)action == ButtonAction::Down)
			_mbuttons.insert((MouseButton)button);
		else if ((ButtonAction)action == ButtonAction::Up)
			_mbuttons.erase((MouseButton)button);

		UserEvent ev;
		ev.type = UserEvent::Type::MouseAction;
		ev.mouse.button = (MouseButton)button;
		ev.mouse.action = (ButtonAction)action;
		_events.push(ev);
	}

	static void _keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
	{
		if ((ButtonAction)action == ButtonAction::Down)
			_holdingKeys.insert((Key)key);
		else if ((ButtonAction)action == ButtonAction::Up)
			_holdingKeys.erase((Key)key);

		UserEvent ev;
		ev.type = UserEvent::Type::Key;
		ev.key.value = (Key)key;
		ev.key.action = (ButtonAction)action;
		_events.push(ev);
	}

	static void _windowResizeCallback(GLFWwindow* window, int w, int h)
	{
		_size = vec2{w, h} * _contentScale;
		glViewport(0, 0, _size.x, _size.y) ERROR_CHECKED;
		_defTarget._setSize(_size.x, _size.y);

		UserEvent ev;
		ev.type = UserEvent::Type::Resize;
		_events.push(ev);
	}

	static void _windowReposeCallback(GLFWwindow* window, int x, int y)
	{
		UserEvent ev;
		ev.type = UserEvent::Type::Reposition;
		_events.push(ev);
	}

	static void _windowContentScaleCallback(GLFWwindow* window, float xscale, float yscale)
	{
		_contentScale = { xscale, yscale };
	}
};

stack<RenderBuffer*> Context::Impl::_targets;

GLFWwindow* Context::Impl::_window = nullptr;
wstring Context::Impl::_title = L"";
Shape* Context::Impl::_screen = nullptr;
RenderBuffer Context::Impl::_defTarget;

vec2 Context::Impl::_size{};
vec2 Context::Impl::_contentScale{};
bool Context::Impl::_inited = false;

vec2 Context::Impl::_scissor{};
vec2 Context::Impl::_scissorSize{};
vec2 Context::Impl::_mousePos{};

queue<UserEvent> Context::Impl::_events;

set<MouseButton> Context::Impl::_mbuttons;
set<Key> Context::Impl::_holdingKeys;

double Context::Impl::_dt = 0.05;
double Context::Impl::_dampedDt = 0.05;
double Context::Impl::_frameStart = 0;
map<KnownCursor, GLFWcursor*> Context::Impl::_knownCursors;
GLFWcursor* Context::Impl::_cursor = nullptr;

double Context::maxFramerate = 80;

void Context::init(vec2 resolution, const MonitorInfo* mon)
{
	if (Impl::_inited)
		throw logic_error("anip::Context already initialized");

	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GLVersionMajor);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, GLVersionMinor);
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);

	GLFWmonitor *hmon = nullptr;
	if (mon != nullptr) hmon = (GLFWmonitor*)mon->_handle;
	Impl::_window = glfwCreateWindow(100, 100, "", hmon, nullptr);
	
	if (!Impl::_window)
		throw runtime_error("Window creation failed");

	glfwSetCursorPosCallback			(Impl::_window, Impl::_cursorPosCallback);
	glfwSetMouseButtonCallback			(Impl::_window, Impl::_mouseActionCallback);
	glfwSetScrollCallback				(Impl::_window, Impl::_scrollCallback);
	glfwSetDropCallback					(Impl::_window, Impl::_dropCallback);
	glfwSetCharCallback					(Impl::_window, Impl::_unicodeCallback);
	glfwSetWindowSizeCallback			(Impl::_window, Impl::_windowResizeCallback);
	glfwSetKeyCallback					(Impl::_window, Impl::_keyCallback);
	glfwSetWindowPosCallback			(Impl::_window, Impl::_windowReposeCallback);
	glfwSetWindowContentScaleCallback	(Impl::_window, Impl::_windowContentScaleCallback);

	glfwMakeContextCurrent(Impl::_window);

	if (!gladLoadGL())
		throw runtime_error("GLAD initialization failed");

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_SCISSOR_TEST);

	glPixelStorei(GL_PACK_ALIGNMENT, 1);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	float xscale, yscale;
	glfwGetWindowContentScale(Impl::_window, &xscale, &yscale);
	Impl::_contentScale = { xscale, yscale };
	setSize(resolution);

	noScissor();
	setCursor(KnownCursor::Arrow);

	Draw::_init();
}

MonitorInfo Context::getPrimaryMonitor()
{
	return MonitorInfo(glfwGetPrimaryMonitor());
}

const vector<MonitorInfo> Context::getMonitors()
{
	int n; vector<MonitorInfo> result;
	GLFWmonitor** mons = glfwGetMonitors(&n);
	for (int i = 0; i < n; i++)
		result.push_back(MonitorInfo(mons[i]));
	return result;
}

MonitorInfo Context::findCurrentMonitor()
{
	auto mons = getMonitors();
	for (auto& i : mons)
		if (math::inRectangle(position(), i.position(), i.resolution()))
			return i;
	throw runtime_error("Window is invisible on any of the monitors");
}

void Context::_pushBuffer(RenderBuffer& buf)
{
	Impl::_targets.push(&buf);
}

RenderBuffer* anip::Context::_popBuffer()
{
	assert(!Context::Impl::_targets.empty());

	RenderBuffer* buf = Context::Impl::_targets.top();
	Context::Impl::_targets.pop();
	return buf;
}

vec2 Context::size() { return Impl::_size; }
wstring Context::title() { return Impl::_title; }

void Context::setSize(invec2 size)
{
	vec2 unscaled = size / Impl::_contentScale;
	glfwSetWindowSize(Impl::_window, (GLsizei)unscaled.x, (GLsizei)unscaled.y);
	glViewport(0, 0, (GLsizei)size.x, (GLsizei)size.y);
	Impl::_size = size;

	Impl::_defTarget._setSize((int)size.x, (int)size.y);
}

void Context::finalize()
{
	if (Impl::_screen != nullptr)
		Impl::_screen->finalize();
		delete Impl::_screen;
	// no need to free screen (dummy) target
	glfwDestroyWindow(Impl::_window);
}

void Context::setTitle(const wstring& title)
{
	if (Impl::_title != title)
	{
		Impl::_title = title;
		glfwSetWindowTitle(Impl::_window, ws2utf8s(title).c_str());
	}
}

vec2 Context::position()
{
	int x, y;
	glfwGetWindowPos(Impl::_window, &x, &y);
	return vec2{ (float)x, (float)y } * Impl::_contentScale;
}

vec2 anip::Context::contentScale()
{
    return Impl::_contentScale;
}

void Context::setPosition(invec2 pos)
{
	vec2 unscaled = pos / Impl::_contentScale;
	glfwSetWindowPos(Impl::_window, (int)unscaled.x, (int)unscaled.y);
}

void anip::Context::setCursor(KnownCursor c)
{
	if (c == KnownCursor::None)
	{
		Impl::_cursor = nullptr;
		return;
	}
	auto res = Impl::_knownCursors.find(c);
	if (res == Impl::_knownCursors.end())
	{
		Impl::_cursor = glfwCreateStandardCursor((int)c);
		Impl::_knownCursors.insert(make_pair(c, Impl::_cursor));
	}
	else Impl::_cursor = res->second;
}

bool Context::pollEvent(UserEvent& ev)
{
	if (Impl::_events.empty()) return false;
	ev = Impl::_events.front();
	Impl::_events.pop();
	return true;
}

vec2 Context::mousePos()
{
	return Impl::_mousePos;
}

void Context::setMousePos(invec2 pos)
{
	vec2 unscaled = pos / Impl::_contentScale;
	glfwSetCursorPos(Impl::_window, unscaled.x, unscaled.y);
	Impl::_mousePos = pos;
}

const set<Key> anip::Context::getHoldingKeys()
{
	return Impl::_holdingKeys;
}

bool anip::Context::holdingShift()
{
	return Impl::_holdingKeys.count(Key::LeftShift) +
		Impl::_holdingKeys.count(Key::RightShift) > 0;
}

bool anip::Context::holdingControl()
{
	return Impl::_holdingKeys.count(Key::LeftControl) +
		Impl::_holdingKeys.count(Key::RightControl) > 0;
}

bool anip::Context::holdingAlt()
{
	return Impl::_holdingKeys.count(Key::LeftAlt) +
		Impl::_holdingKeys.count(Key::RightAlt) > 0;
}

bool Context::holdingMouseButton(MouseButton btn)
{
	return Impl::_mbuttons.count(btn) == 1;
}

RenderBuffer& Context::defaultTarget() { return Impl::_defTarget; };

RenderBuffer& anip::Context::currentTarget()
{
	return Impl::_targets.empty() ? Impl::_defTarget : *Impl::_targets.top();
}

bool Context::windowShouldClose() { return glfwWindowShouldClose(Impl::_window); }

void anip::Context::markWindowShouldClose()
{
	glfwSetWindowShouldClose(Impl::_window, 1);
}

void Context::clear()
{
	glClear(GL_COLOR_BUFFER_BIT) ERROR_CHECKED;
}

void Context::clear(color c)
{
	static color clearColor = { -1, -1, -1, -1 };
	if (c != clearColor)
	{
		clearColor = c;
		glClearColor(c.r, c.g, c.b, c.a) ERROR_CHECKED;
	}
	glClear(GL_COLOR_BUFFER_BIT) ERROR_CHECKED;
}

void Context::fill()
{
	if (Impl::_screen == nullptr)
	{
		static const float vertices[8] = {
			-1.0f, -1.0f,
			 1.0f, -1.0f,
			-1.0f,  1.0f,
			 1.0f,  1.0f
		};
		Impl::_screen = new Shape(Shape::Type::TriangleStrip, vertices, 4);
	}
	Impl::_screen->draw();
}

void Context::show()
{
	glfwShowWindow(Impl::_window);
}

void Context::showFullscreen(MonitorInfo* mon)
{
	glfwSetWindowMonitor(Impl::_window,
		mon == nullptr ? nullptr : (GLFWmonitor*)(mon->_handle),
		50, 50, (GLsizei)Impl::_size.x, (GLsizei)Impl::_size.y, GLFW_DONT_CARE);
	glfwShowWindow(Impl::_window);
}

void anip::Context::startFrame()
{
	Impl::_frameStart = time();
}

void anip::Context::endFrame()
{
	constexpr double damping = 0.8;
	constexpr double latencyDamping = 0.8;
	static double latency = 0;

	double secs = 1.0 / maxFramerate - time() + Impl::_frameStart - latency;
	if (secs > 0)
		this_thread::sleep_for(chrono::duration<double>(secs));

	double d = time() - Impl::_frameStart;
	Impl::_dt = d;
	Impl::_dampedDt = damping * Impl::_dampedDt + (1 - damping) * d;

	latency = latency * latencyDamping + (d - 1.0 / maxFramerate) * (1 - latencyDamping);
}

double anip::Context::deltaTime()
{
	return Impl::_dt;
}

double anip::Context::dampedDeltaTime()
{
	return Impl::_dampedDt;
}

void Context::setScissor(invec2 p, invec2 s, bool absolute)
{
	if (!absolute)
	{
		Transform2D tran = currentTarget().transform();
		tran.transformBoundingRect(p, s);
	}

	if (p == Impl::_scissor || s == Impl::_scissorSize)
		return;
	Impl::_scissor = p;
	Impl::_scissorSize = s;

	glScissor(
		(int)Impl::_scissor.x, 
		(int)(Impl::_size.y - Impl::_scissor.y - Impl::_scissorSize.y),
		(int)Impl::_scissorSize.x, 
		(int)Impl::_scissorSize.y) ERROR_CHECKED;
}

void Context::addScissor(invec2 p, invec2 s, bool absolute)
{
	if (!absolute)
	{
		Transform2D tran = currentTarget().transform();
		tran.transformBoundingRect(p, s);
	}

	vec2 np, ns;
	math::rectangleIntersect(Impl::_scissor, Impl::_scissorSize, 
		p, s, /*out*/ np, ns);
	setScissor(np, ns, true);
}

void Context::getScissor(outvec2 p, outvec2 s, bool absolute)
{
	p = Impl::_scissor;
	s = Impl::_scissorSize;
	
	if (!absolute)
	{
		Transform2D tran = currentTarget().transform();
		tran.undoTransformBoundingRect(p, s);
	}
}

void Context::noScissor()
{
	Impl::_scissor = { 0, 0 };
	Impl::_scissorSize = size();
	glScissor(0, 0, (int)Impl::_size.x, (int)Impl::_size.y) ERROR_CHECKED;
}

void Context::update()
{
	// ANIP_FIXME: it seems to create two independent buffers that will
	// be alternatively rendered to, so continue rendering on the same
	// buffer for consecutive frames will be cumbersome.
	glfwSwapBuffers(Impl::_window);
}

void Context::doEvents(bool wait)
{
	static GLFWcursor* lastCursor = nullptr;
	if (lastCursor != Impl::_cursor) 
	{
		if (Impl::_cursor == nullptr)
			glfwSetInputMode(Impl::_window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
		else
		{
			glfwSetCursor(Impl::_window, Impl::_cursor);
			if (lastCursor == nullptr)
				glfwSetInputMode(Impl::_window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
		}
		lastCursor = Impl::_cursor;
	}
	if (wait)
		glfwWaitEvents();
	else
		glfwPollEvents();
}
